#!/usr/bin/python
# -*- coding: utf-8 -*-

import re
import inspect


'''
参数验证器
该模块提供了一个装饰器用于验证参数是否合法

from valid_param import valid_param, multi_type, null_type

使用方法为:
    1. 验证参数类型
        @valid_param(type, ...)
        或
        @valid_param(type, key=type, ...)
        
        1.1. 检查第一个位置的参数是否为 int 类型, 第二个位置的参数是否为 str 类型
            @valid_param(int, str)
            def func(i, s): pass
        1.2. 检查名为 i 的参数是否为 int 类型, 名为 x 的参数是否为 str 类型
            @valid_param(i=int, s=str)
            def func(i, s): pass
        1.3. 检查第一个位置的参数是否为 int 类型, 名为 x 的参数是否为 str 类型
            @valid_param(int, s=str)
            def func(i, s): pass
        1.4. 检查 变长参数 args 的类型, 第一个变长参数类型为 dict, 第二个变长参数类型为 str, 第三个变长参数类型为 int
            @valid_param(int, dict, str, int)
            def func(i, *args): pass
        1.5. 检查 变长参数 args 的类型, 第一个变长参数类型为 dict, 第二个变长参数类型为 str, 第三个变长参数类型为 int
             且 检查 变长参数 kwargs 的类型, key 为 name 的参数类型为 str, key 为 age 的参数类型为 int
            @valid_param(int, dict, str, int, name=str, age=int)
            def func(i, *args, **kwargs): pass
        1.6. 检查 变长参数 args 的第一个变长参数类型为 dict
             且 检查 变长参数 kwargs 的类型, key 为 name 的参数类型为 str, key 为 age 的参数类型为 int
            @valid_param(int, dict, name=str, age=int)
            def func(i, *args, **kwargs): pass
            也可以这样写:
            @valid_param(int, args=dict, name=str, age=int)
            def func(i, *args, **kwargs): pass
            
    2. 条件验证
        @valid_param((type, condition), ...)
        其中, type 为参数类型
        condition 可以是一个表达式字符串, 使用 x 引用待验证的对象, 根据 bool(表达式的值)判断是否通过验证
        另外, 如果 condition 是字符串, condition 还可以使用斜杠开头和结尾表示正则表达式匹配.
        condition 也可以是一个 拥有一个参数的函数, 根据返回值来判断是否通过验证
        
        2.1. 参数 i 是一个 10 到 20 之间的整数
            @valid_param(i=(int, '10<x<20'))
            def func(i, s): pass
        2.2. 参数 s 是一个长度小于 20 的字符串
            @valid_param(s=(str, 'len(x)<20'))
            def func(i, s): pass
        2.3. 参数 stu 是一个年龄小于 20 的学生
            @valid_param(stu=(Student, 'x.age<20'))
            def func(i, s, stu): pass
        2.4. 参数 s 是一个由数字组成的字符串
            @valid_param(s=(str, '/^\d*$/'))
            def func(i, s): pass
        2.5. 参数 s 需要通过 函数 f1 来验证是否有效
            def f1(s): return len(s) > 20
            @valid_param(s=f1)
            def func(i, s): pass
            
    3. 参数的多个类型验证
        3.1 第一个位置的参数为 int 或 str 类型
            @valid_param(multi_type(int, str))
            def func(i, s): pass
        3.2 名称为 s 的参数为 int 或 str 或 dict 或 None 类型
            @valid_param(s=multi_type(int, str, dict, None))
            def func(i, s): pass
        3.3 名称为 s 的参数为 str 或 None 类型
            @valid_param(s=multi_type(str, None))
            def func(i, s): pass
            也可以这样写
            @valid_param(s=null_type(str))
            def func(i, s): pass
        3.4 名称为 s 的参数类型为 str 或 None 类型,
            当为 str 类型时, 字符串中的内容全是数字
            @valid_param(s=null_type(str, '/^\d*$/'))
            def func(i, s): pass
        3.4 名称为 s 的参数类型为 int 或 str 或 dict 类型
            当为 int 类型时, 值大于等于 20
            当为 str 类型时, 字符串中的内容全是数字
            @valid_param(s=multi_type((int, 'x>=20'), (str, '/^\d*$/'), dict))
            def func(i, s): pass
'''


class ValidateException(Exception):
    pass


def valid_param(*valid_varargs, **valid_varkw):
    valid_func_varargs = tuple(map(_condition_convert_function, valid_varargs))
    valid_func_varkw = dict((k, _condition_convert_function(valid_varkw[k])) for k in valid_varkw)

    def wrapper(func):
        def generator(*call_varargs, **call_varkw):
            func_args, func_varargs_name, func_varkw_name, func_defaults = inspect.getfullargspec(func)[:4]
            valid_func_dict = _generator_args_dict(func_args, func_varargs_name, None, valid_func_varargs, valid_func_varkw)
            call_value_dict = _generator_args_dict(func_args, func_varargs_name, func_defaults, call_varargs, call_varkw)
            key, valid_func, call_value = None, None, None
            try:
                for key, valid_item in valid_func_dict.items():
                    if key == func_varargs_name:
                        call_item = call_value_dict.get(key)
                        for n, valid_func in enumerate(valid_item):
                            if n >= len(call_item):
                                break
                            call_value = call_item[n]
                            assert valid_func(call_value)
                    else:
                        valid_func = valid_item
                        call_value = call_value_dict.get(key)
                        assert valid_func(call_value)
            except:
                raise ValidateException('%s() parameter validation failure. param:%s value: %s(%s)'
                                        % (func.__name__, key, call_value, call_value.__class__.__name__))
            return func(*call_varargs, **call_varkw)
        return generator

    return wrapper


def null_type(cls, condition=None):
    '''这个函数指定的检查条件可以接受None值'''

    return lambda x: x is None or _condition_convert_function((cls, condition))(x)


def multi_type(*conditions):
    '''这个函数指定的检查条件只需要有一个通过即可'''

    valid_tuple = tuple(map(_condition_convert_function, conditions))

    def validate(x):
        for n, v in enumerate(valid_tuple):
            if v(x):
                return True
    return validate


def _condition_convert_function(condition):
    '''
    将 检查条件 转换为 检查函数
    :param condition: 条件, 例如 int / str / (str, 'len(x)<20') / (int, '10<x<20')
    :return: lambda 函数
    '''

    # 当 condition 为 class 时
    if inspect.isclass(condition):
        return lambda x: isinstance(x, condition)

    # 当 condition 为 None 时
    if condition is None:
        return lambda x: x is None

    # 当 condition 为 tuple or list 时
    if isinstance(condition, (tuple, list)):
        cls, condition = condition[:2]

        # 当第二个 condition 为 None 时
        if condition is None:
            return _condition_convert_function(cls)

        # 当第二个 condition 为 str, 且第一个字符与最后一个字符均为 / 时, 当做 正则条件
        if cls == str and condition[0] == condition[-1] == '/':
            return lambda x: (isinstance(x, cls) and re.match(condition[1:-1], x) is not None)

        # 将第二个 condition 当做表达式条件
        return lambda x: isinstance(x, cls) and eval(condition)

    # 直接返回 condition, 可能是 function
    return condition


def _generator_args_dict(func_args, func_varargs, func_defaults, args, kwargs):
    args_dict = {}

    func_args_count = len(func_args)
    args_count = len(args)

    # 将 默认值 塞到 字典中
    if func_defaults is not None:
        for n, var in enumerate(func_defaults[::-1]):
            args_dict[func_args[-n-1]] = var

    # 将 tuple 实参 塞到 字典中
    if func_args_count >= args_count:
        # 如果函数的形参数量 大于等于 实参数量
        for n, var in enumerate(args):
            args_dict[func_args[n]] = var
    else:
        # 如果函数的形参数量 小于 实参数量
        for n, var in enumerate(func_args):
            args_dict[var] = args[n]
        args_dict[func_varargs] = args[func_args_count:]

    # 将 dict实参 塞到 字典中
    for key, value in kwargs.items():
        if key == func_varargs:
            args_dict[key] = [value]
        else:
            args_dict[key] = value

    return args_dict


#===============================================================================
# 测试
#===============================================================================


def _functest(func, *args, **kws):
    try:
        func(*args, **kws)
    except ValidateException as e:
        print(e)


def test_valid_type():

    print('\nvalid function f1:')
    @valid_param(int, str)
    def f1(i, s): pass
    _functest(f1, 1, 'b')
    _functest(f1, 1, 2)
    _functest(f1, 'a', 'b')
    _functest(f1, 'a', 2)

    print('\nvalid function f2:')
    @valid_param(int, dict, str, int)
    def f2(i, *args): pass
    _functest(f2, 1, {'hello':'world'}, 'c', 4)
    _functest(f2, 1, {'hello':'world'})
    _functest(f2, 1, {'hello':'world'}, 3)
    _functest(f2, 1, {'hello':'world'}, 'c', 'd')

    print('\nvalid function f3:')
    @valid_param(int, name=str, age=int)
    def f3(i, **kwargs): pass
    _functest(f3, 1, name='jiawa', age=23)
    _functest(f3, 1, name=2, age=23)
    _functest(f3, 1, name='jiawa', age='zhu')


# 条件验证
def test_valid_condition():

    print('\nvalid function condition1:')
    @valid_param(i=(int, '10<x<20'))
    def condition1(i, s): pass
    _functest(condition1, 11, 'b')
    _functest(condition1, 10, 'b')
    _functest(condition1, 20, 'b')

    print('\nvalid function condition2:')
    @valid_param(s=(str, 'len(x)<20'))
    def condition2(i, s): pass
    _functest(condition2, 1, 'abcdefghi')
    _functest(condition2, 1, 'abcdefghijkmnopqrstuvwxyz')

    print('\nvalid function condition3:')
    class Student: pass
    @valid_param(stu=(Student, 'x.age<20'))
    def condition3(i, s, stu): pass
    stu = Student()
    stu.age = 19
    _functest(condition3, 1, 'abcdefghi', stu)
    stu.age = 20
    _functest(condition3, 1, 'abcdefghi', stu)

    print('\nvalid function condition4:')
    @valid_param(s=(str, '/^\d*$/'))
    def condition4(i, s): pass
    _functest(condition4, 1, '123456789000')
    _functest(condition4, 1, '123456789000a')

    print('\nvalid function condition5:')
    def f1(s): return len(s) > 20
    @valid_param(s=f1)
    def condition5(i, s): pass
    _functest(condition5, 1, 'abcdefghijkmnopqrstuvwxyz')
    _functest(condition5, 1, 'abcdefghi')



def test_valid_multi_type():

    print('\nvalid function multi1:')
    @valid_param(multi_type(int, str))
    def multi1(i): pass
    _functest(multi1, 1)
    _functest(multi1, 'a')
    _functest(multi1, ())

if __name__ == '__main__':
    test_valid_type()
    test_valid_condition()
    test_valid_multi_type()